#
# "@(#) $Id: Burner.py,v 1.1.1.1 2006-07-27 03:16:08 gioshe Exp $"
#
# This work is released under the GNU GPL, version 2 or later.
#
import os,re
import wave
from CD.Process import *
from Utils.Settings import *

class  MusicConverterProcess(Process):
	def __init__(self,track,index = 0,count = 0):
		Process.__init__(self)
		self.track = track
		self.index = index
		self.count = count
		path = os.environ.get('TMP','/tmp')
		self.cdrPath = os.path.join(path,("Track%d.wav" % index))
	
	def buildCommand(self):
		self.emitEvent({'Source':'Burner','Status':'Converting','currentTime':self.index,'totalTime':self.count,'Track':self.track})

	def endProcess(self,process):
		stats = os.stat(self.cdrPath)
		self.size = stats[6]/1024/1024
		Process.endProcess(self,process)
		
	def abortProcess(self,process):
		try: os.remove(self.cdrPath)
		except: pass
		Process.abortProcess(self,process)

class  MusicTranscodeProcess(MusicConverterProcess):
	def __init__(self,track,index = 0,count = 0):
		MusicConverterProcess.__init__(self,track,index,count)
		try: os.remove(self.cdrPath)
		except: pass

class  MusicTranscodeMP3Process(MusicTranscodeProcess):
	def buildCommand(self):
		self.setExecutable('nice')
		args = ['mpg321','-v',self.track.location,'--wav',self.cdrPath]
		self.setArguments(args)
		self.pattern = re.compile(r"^Frame\#\s*?(\d+)\s*?\[\s*?(\d+)\]")
		MusicTranscodeProcess.buildCommand(self)
		self.convertLineEndings = True
	
	def processLines(self,lines):
		line = lines[-1]
		mo = self.pattern.match(line)
		if mo:
			currentTime = long(mo.group(1))
			totalTime = long(mo.group(2))+currentTime
			fraction = 1.0*currentTime/totalTime
			self.maybeEmitEvent({'Source':'Burner','Status':'Converting','currentTime':self.index+fraction,'totalTime':self.count,'Track':self.track})

class  MusicTranscodeMP4Process(MusicTranscodeProcess):
	def buildCommand(self):
		self.setExecutable('nice')
		args = ['faad','-f','1','-o',self.cdrPath,self.track.location]
		self.setArguments(args)
		self.pattern = re.compile(r"^(\d+)%\sdecoding")
		MusicTranscodeProcess.buildCommand(self)
		self.convertLineEndings = True
	
	def processLines(self,lines):
		line = lines[-1]
		mo = self.pattern.match(line)
		if mo:
			currentTime = long(mo.group(1))
			totalTime = 100
			fraction = 1.0*currentTime/totalTime
			self.maybeEmitEvent({'Source':'Burner','Status':'Converting','currentTime':self.index+fraction,'totalTime':self.count,'Track':self.track})

class MusicTranscodeOggProcess(MusicTranscodeProcess):
	def buildCommand(self):
		self.setExecutable('nice')
		args = ['sox',self.track.location,self.cdrPath]
		self.setArguments(args)
		MusicTranscodeProcess.buildCommand(self)

class MusicTranscodeWavProcess(MusicTranscodeProcess):
	def buildCommand(self):
		self.setExecutable('nice')
		args = ['sox',self.track.location,self.cdrPath]
		self.setArguments(args)
		MusicTranscodeProcess.buildCommand(self)

class MusicResampleProcess(MusicConverterProcess):
	def buildCommand(self):
		#print "resampling",self.cdrPath
		self.newPath = re.sub(".wav","x.wav",self.cdrPath)
		os.rename(self.cdrPath,self.newPath)
		self.setExecutable('nice')
		args = ["sox",self.newPath,'-r','44100','-c','2',self.cdrPath,'resample','-qs']
		self.setArguments(args)
		MusicConverterProcess.buildCommand(self)
	
	def endProcess(self,process):
		try: os.remove(self.newPath)
		except: pass
		MusicConverterProcess.endProcess(self,process)
	
	def abortProcess(self,process):
		try: os.remove(self.newPath)
		except: pass
		MusicConverterProcess.endProcess(self,process)

class BurnAudioProcess(Process):
	def __init__(self,tracksInfo,burner,speed):
		self.tracksInfo = tracksInfo
		self.burner = burner
		self.speed = speed
		self.pattern = re.compile(r"^Track\s*?(\d+)\:\s*?(\d+)\sof\s*?(\d+)\sMB")
		Process.__init__(self)
		self.convertLineEndings = True
		
	def buildCommand(self):
		self.setExecutable('nice')
		args = ['cdrecord','-v','-eject','-audio','-pad']
		args.append("dev=%s" % self.burner['Address'])
		args.append("speed=%d" % self.speed)
		args.append("driveropts=burnfree")
		self.totalTime = 0
		for trackInfo in self.tracksInfo:
			args.append(trackInfo['Path'])
			trackInfo['Base'] = self.totalTime
			self.totalTime = self.totalTime+trackInfo['Size']
		self.setArguments(args)
		self.emitEvent({'Source':'Burner','Status':'StartBurning'})
	
	def processLines(self,lines):
		#for line in lines:
		#	print line
		line = lines[-1]
		mo = self.pattern.match(line)
		if mo:
			index = long(mo.group(1))
			if index<=len(self.tracksInfo):
				item = self.tracksInfo[index-1]
				track = item['Track']
				currentTime = long(mo.group(2))+item['Base']
				self.maybeEmitEvent({'Source':'Burner','Status':'Burning','currentTime':currentTime,'totalTime':self.totalTime,'Track':track})

	def endProcess(self,process):
		for trackInfo in self.tracksInfo:
			try: os.remove(trackInfo['Path'])
			except: pass
		self.emitEvent({'Source':'Burner','Status':'EndBurning'})
		Process.endProcess(self,process)

	def abortProcess(self,process):
		for trackInfo in self.tracksInfo:
			try: os.remove(trackInfo['Path'])
			except: pass
		self.emitEvent({'Source':'Burner','Status':'EndBurning','Interrupted':True})
		Process.abortProcess(self,process)

class MusicMakeIsoProcess(Process):
	def __init__(self,tracksInfo):
		self.tracksInfo = tracksInfo
		Process.__init__(self)
		self.pattern = re.compile(r"^\s*?(\d+)\.")
		path = os.environ.get('TMP','/tmp')
		self.isoPath = os.path.join(path,"CD.iso")
	
	def buildCommand(self):
		self.setExecutable('nice')
		args = ['mkisofs','-v','-r','-o',self.isoPath]
		for trackInfo in self.tracksInfo:
			args.append(trackInfo['Path'])
		#print "MusicMakeIsoProcess",args
		self.setArguments(args)
		self.emitEvent({'Source':'Burner','Status':'Begin'})

	def processLines(self,lines):
		#print lines
		line = lines[-1]
		mo = self.pattern.match(line)
		if mo:
			currentTime = long(mo.group(1))
			self.maybeEmitEvent({'Source':'Burner','Status':'Building Image','currentTime':currentTime,'totalTime':100})

	def endProcess(self,process):
		print "done mkisofs"
		self.emitEvent({'Source':'Burner','Status':'Built Image'})
		Process.endProcess(self,process)

	def abortProcess(self,process):
		print "abort mkisofs"
		try: os.remove(self.isoPath)
		except: pass
		self.emitEvent({'Source':'Burner','Status':'Built Image','Interrupted':True})
		Process.abortProcess(self,process)
	
class BurnIsoProcess(Process):
	def __init__(self,burner,speed):
		self.burner = burner
		self.speed = speed
		self.pattern = re.compile(r"^Track\s*?(\d+)\:\s*?(\d+)\sof\s*?(\d+)\sMB")
		Process.__init__(self)
		path = os.environ.get('TMP','/tmp')
		self.isoPath = os.path.join(path,"CD.iso")
		stats = os.stat(self.isoPath)
		self.totalTime = stats[6]/1024/1024
		self.convertLineEndings = True
	
	def buildCommand(self):
		self.setExecutable('nice')
		args = ['cdrecord','-v','-eject']
		#args.append["-dummy"]
		args.append("dev=%s" % self.burner['Address'])
		args.append("speed=%d" % self.speed)
		args.append(self.isoPath)
		self.setArguments(args)
		self.emitEvent({'Source':'Burner','Status':'Burning Image','currentTime':0,'totalTime':100})
	
	def processLines(self,lines):
		line = lines[-1]
		#print line
		mo = self.pattern.match(line)
		if mo:
			currentTime = long(mo.group(2))
			self.maybeEmitEvent({'Source':'Burner','Status':'Burning Image','currentTime':currentTime,'totalTime':self.totalTime})

	def endProcess(self,process):
		try: os.remove(self.isoPath)
		except: pass
		self.emitEvent({'Source':'Burner','Status':'EndBurning'})
		Process.endProcess(self,process)

	def abortProcess(self,process):
		try: os.remove(self.isoPath)
		except: pass
		self.emitEvent({'Source':'Burner','Status':'EndBurning','Interrupted':True})
		Process.abortProcess(self,process)

class Burner(QObject):
	def __init__(self):
		QObject.__init__(self)
		self.tracksToTranscode = []
		self.tracksToBurn = []
		self.process = None

	def gotStatus(self,process,status):
		self.emit(PYSIGNAL('status'),(status,None))

	def transcoderForTrack(self,track,index,count):
		trackType = track.kind.split()[0]
		if trackType=='MPEG':
			return MusicTranscodeMP3Process(track,index,count)
		elif trackType=='MPEG-4':
			return MusicTranscodeMP4Process(track,index,count)
		elif trackType=='Ogg':
			return MusicTranscodeOggProcess(track,index,count)
		elif trackType=='WAV':
			return MusicTranscodeWavProcess(track,index,count)
		print "can't transcode",path
		return None

	def transcodeNextTrack(self):
		if (self.process==None or not self.process.isRunning()):
			if len(self.tracksToTranscode)>0:
				if self.index==0:
					self.gotStatus(None,{'Source':'Burner','Status':'Begin'})
				track = self.tracksToTranscode[0]
				self.tracksToTranscode = self.tracksToTranscode[1:]
				self.process = self.transcoderForTrack(track,self.index,self.count)
				QObject.connect(self.process,PYSIGNAL("done"),self.endTranscodeTrack)
				QObject.connect(self.process,PYSIGNAL("status"),self.gotStatus)
				self.process.run()
				self.index = self.index+1
			else:
				self.startBurning()
		else:
			print "burning is busy!"
	
	def endTranscodeTrack(self,process):
		if not process.interrupted:
			fd = wave.open(process.cdrPath,"rb")
			(numChannels,sampleWidth,frameRate,numFrames,compressionType,compressionName) = fd.getparams()
			if frameRate!=44100:
				self.startResample(process.track,process.index,process.count)
			else:
				self.tracksToBurn.append({"Path":process.cdrPath,'Size':process.size,'Track':process.track})
				self.transcodeNextTrack()
	
	def startResample(self,track,index =0,count = 0):
		if (self.process==None or not self.process.isRunning()):
			self.process = MusicResampleProcess(track,index,count)
			QObject.connect(self.process,PYSIGNAL("done"),self.endResampling)
			QObject.connect(self.process,PYSIGNAL("status"),self.gotStatus)
			self.process.run()
	
	def endResampling(self,process):
		if not process.interrupted:
			self.tracksToBurn.append({"Path":process.cdrPath,'Size':process.size,'Track':process.track})
			self.transcodeNextTrack()
	
	def startBurning(self):
		if (self.process==None or not self.process.isRunning()):
			self.process = BurnAudioProcess(self.tracksToBurn,self.burner,self.speed)
			QObject.connect(self.process,PYSIGNAL("done"),self.endBurning)
			QObject.connect(self.process,PYSIGNAL("status"),self.gotStatus)
			self.process.run()

	def endBurning(self,process):
		self.tracksToBurn = []
		self.gotStatus(None,{'Source':'Burner','Status':'End'})
	
	def burnTracks(self,tracks,burner,speed):
		self.burner = burner
		self.speed = speed
		settings = LSettings.settings()
		type = settings.get('Burn Disc Type','audio')
		if type=='audio':
			self.burnAudioTracks(tracks)
		elif type=='mp3':
			self.burnMP3Tracks(tracks)
		elif type=='data':
			self.burnDataTracks(tracks)
		else:
			print "unknown disc type"
		
	def burnAudioTracks(self,tracks):
		self.tracksToTranscode = tracks[0:]
		self.index = 0
		self.count = len(tracks)
		self.transcodeNextTrack()

	def burnMP3Tracks(self,tracks):
		if tracks and len(tracks)>0 :
			tracksInfo = []
			for track in tracks:
				trackType = track.kind.split()[0]
				if trackType=='MPEG':
					tracksInfo.append({'Path':track.location})
			if len(tracksInfo)>0:
				self.process = MusicMakeIsoProcess(tracksInfo)
				QObject.connect(self.process,PYSIGNAL("done"),self.endMakeDataIso)
				QObject.connect(self.process,PYSIGNAL("status"),self.gotStatus)
				self.process.run()
	
	def burnDataTracks(self,tracks):
		if tracks and len(tracks)>0:
			tracksInfo = []
			for track in tracks:
				tracksInfo.append({'Path':track.location})
			self.process = MusicMakeIsoProcess(tracksInfo)
			QObject.connect(self.process,PYSIGNAL("done"),self.endMakeDataIso)
			QObject.connect(self.process,PYSIGNAL("status"),self.gotStatus)
			self.process.run()
	
	def endMakeDataIso(self,process):
		if not process.interrupted:
			self.burnIso()

	def burnIso(self):
		try:
			self.process = BurnIsoProcess(self.burner,self.speed)
			QObject.connect(self.process,PYSIGNAL("done"),self.endBurnIso)
			QObject.connect(self.process,PYSIGNAL("status"),self.gotStatus)
			self.process.run()
			self.gotStatus(None,{'Source':'Burner','Status':'BeginIsoBurn'})
		except: pass
	
	def endBurnIso(self,process):
		self.gotStatus(None,{'Source':'Burner','Status':'End'})

	def isBurning(self):
		return (self.process and self.process.isRunning()) or \
			len(self.tracksToTranscode)>0 or \
			len(self.tracksToBurn)>0

	def kill(self):
		self.tracksToTranscode = []
		self.tracksToBurn = []
		if self.process:
			self.process.kill()
			self.process = None
			self.gotStatus(None,{'Source':'Burner','Status':'End'})

	def static_killCurrentBurner():
		Burner.singleton().kill()
	
	killCurrentBurner= staticmethod(static_killCurrentBurner)

	def static_singleton():
		global _burnerSingleton
		return _burnerSingleton

	singleton = staticmethod(static_singleton)

_burnerSingleton = Burner()
